#include "StdAfx.h"
#include "MaterialProject.h"
#include "MaterialSceneParameters.h"
#include "MaterialCompiler.h"
#include "MaterialProjectXmlSerialiser.h"
#include "MaterialTemplateGenerator.h"
#include "MaterialCreateShaderDialog.h"

namespace Lit
{
	namespace Editor
	{
		namespace Material
		{
			const std::string Project::DEFAULT_RESOURCE_GROUP = "MaterialProjectGroup";
			Project::Project(Ui::MaterialEditorClass* _ui, const QStringList &cmd_args)
				: mRoot( NULL ), ui(_ui), mCreated(false), QWidget( _ui->projectDock )
			{
				FileSystem::createDirectories( Material::UserPath() );

				setCommandArguments( cmd_args );

				assert( _ui && "Invalid ui!");
				mSerialiser = new ProjectXmlSerialiser;
				mLayout = new QVBoxLayout( this );
				setLayout( mLayout );

				mTree = new QTreeWidget( this );
				mTools = new ProjectTools( this );
			
				mTools->setFixedHeight( 32 );
				mTree->clear();	
				mTree->setColumnCount( 2 );
				mTree->setColumnWidth( 0, 120 );
				mTree->setHeaderLabels( QStringList() << tr("Filename") << " " );
				mTree->header()->setStretchLastSection( true );

				mLayout->addWidget( mTools );
				mLayout->addWidget( mTree );
				mLayout->setContentsMargins( 5, 0, 5, 5 );
				mTools->setOrientation( Qt::Horizontal );
				
				_ui->projectDock->setWidget( this );

				mTree->installEventFilter( this );
			
				connect( mTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(treeItemDoubleClicked(QTreeWidgetItem*,int)) );
				connect( mTree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(treeItemClicked(QTreeWidgetItem*,int)) );
				
				updateUi();
			}
			Project::~Project(void)
			{
				closeProject();
				mSerialiser.destroy();
			}
			
			
			void Project::setMaterial( const std::string &name ){
				if( mParameters->entity.created() ) mParameters->entity->setMaterialName( name );
			}
			void Project::setMaterial( Ogre::MaterialPtr mat ){
				if( mParameters->entity.created() ) mParameters->entity->setMaterial( mat );
			}
			
			void Project::setMesh( const std::string &meshName, const Lit::Vec3& scale )
			{
				if( !mParameters->created() ) mParameters->create( mScene );
				mParameters->setMesh( meshName, scale );
				mParameters->fitMeshInViewport( mCamera, 1.5f );
			}
			
			const std::vector<std::string> & Project::getCommandArguments() const{ return mArgs; }
			std::vector<std::string> & Project::getCommandArguments(){ return mArgs; }

			void Project::setCommandArguments( const QStringList &args_ )
			{
				QStringList args(args_);
				if( !args.isEmpty() )
				{
					if( FilePath( FROM_QT(args[0]) ).dirName() == FilePath( FileSystem::applicationPath() ).str() )
						args.erase( args.begin() );
					mArgs.clear();
					if( !args.isEmpty() )
					{
						for( auto i=args.begin(); i!=args.end(); i++ )
						{
							mArgs.push_back( FROM_QT((*i)) );
						}
					}
				}
			}
			void Project::createScene()
			{
				mScene = ui->renderWidget->ogreSceneManager();
				mCamera = ui->renderWidget->ogreCamera();
				
				mCamera->setFOVy( Ogre::Degree( 45.0f ) );
				mCamera->setPosition( 100.0f, 1.8f, -100.0f );
				mCamera->lookAt( 0.0f, 0.0f, 0.0f );

				SceneParameters * params = new SceneParameters( Color( 0.9f, 0.9f, 0.9f, 1.0f ), Ogre::SHADOWTYPE_STENCIL_ADDITIVE, 1 );
				params->lights[0] = SceneParameters::Light( Ogre::Light::LT_DIRECTIONAL,  Vec3( 10, 0, 10 ) );
				params->entity.mesh = "cube.mesh";
				params->create( mScene );
				setSceneParameters( params );

				Ogre::SceneNode * n = mScene->getRootSceneNode()->createChildSceneNode();
				n->attachObject( mScene->createEntity( "cube.mesh" ) );
				n->setScale( 100.0f, 0.01f, 100.0f );

				if( !Ogre::ResourceGroupManager::getSingleton().resourceGroupExists( DEFAULT_RESOURCE_GROUP ) )
					Ogre::ResourceGroupManager::getSingleton().createResourceGroup( DEFAULT_RESOURCE_GROUP );
				Ogre::ResourceGroupManager::getSingleton().addResourceLocation( getProjectHome(), "FileSystem", DEFAULT_RESOURCE_GROUP, true );
			}
			void Project::destroyScene()
			{
				if( mParameters->created() )
					mParameters->destroy();
			}
			void Project::prepareResources( )
			{
				if( !Ogre::ResourceGroupManager::getSingleton().resourceGroupExists( DEFAULT_RESOURCE_GROUP ) )
					Ogre::ResourceGroupManager::getSingleton().createResourceGroup( DEFAULT_RESOURCE_GROUP );

				Ogre::ResourceGroupManager::getSingleton().clearResourceGroup( DEFAULT_RESOURCE_GROUP );
			}
			void Project::updateResources( )
			{
				prepareResources( );

				if( Ogre::ResourceGroupManager::getSingleton().resourceLocationExists( getProjectHome(), DEFAULT_RESOURCE_GROUP ) )
					Ogre::ResourceGroupManager::getSingleton().removeResourceLocation( getProjectHome(), DEFAULT_RESOURCE_GROUP );
				Ogre::ResourceGroupManager::getSingleton().addResourceLocation( getProjectHome(), "FileSystem", DEFAULT_RESOURCE_GROUP, true );
				Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup( DEFAULT_RESOURCE_GROUP );
			}
			void Project::setSceneParameters( RefPtr<SceneParameters> params )
			{
				if( !mScene || !params ) return;
				try
				{
					if( mParameters ) mParameters.destroy();
					mParameters = params;
					mParameters->create( mScene );
				}
				catch( std::exception & e )
				{
					Ogre::LogManager::getSingleton().logMessage("Cannot set project scene parameters, exception triggered: " + std::string(e.what()));
				}
			}
			const RefPtr<SceneParameters>& Project::getSceneParameters( ) const
			{
				return mParameters;
			}
			Ogre::Log& Project::logMessage( const std::string &msg, bool debug ){
				Ogre::Log * l( Ogre::LogManager::getSingleton().getDefaultLog() );
				if( l ) l->logMessage( msg, Ogre::LML_NORMAL, debug );
				return *l;
			}
			void Project::compile(const std::string &name){
				if( !hasFile(name) ) return;
				ProjectFilePtr f(getFile( name ));
				std::string ext = FilePath(name).extension();
				if( f )
				{
					Compiler comp;
					try
					{
						if( ext == "material" )
						{
							Ogre::MaterialPtr mat(comp.compileMaterial( f->getName(), f->getText() ));
							setMaterial( mat );
						}
						else if( ext == "program" )
						{
							Ogre::GpuProgramPtr prog(comp.compileProgram( f->getName(), f->getText() ));
						}
					}
					catch( std::exception & e )
					{
						Ogre::LogManager::getSingleton().logMessage("Cannot compile file because "+std::string(e.what()));
					}
					catch( ... )
					{
						Ogre::LogManager::getSingleton().logMessage("Cannot compile file because unknown error happened");
					}
				}
			}
			void Project::compileProject(){
				updateResources( );
				logMessage("Compiling project '"+getName()+"'");
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
					compile( i->first );
			}
			std::string Project::getProjectHome() const{
				return Material::UserPath() + "/" + getName();
			}
			FilePath Project::getFilePath( const std::string &name ) const
			{
				return getProjectHome() + "/" + name;
			}
			
			bool Project::eventFilter(QObject*obj, QEvent*evt)
			{
				if( obj == (QObject*)mTree && evt->type() == QEvent::ContextMenu )
				{
					mTools->execContextMenu( this, mTree->currentItem() );
				}
				return false;
			}
			void Project::setupUi( const std::string &name )
			{
				if( mTree )
				{
					mTree->clear();	
					mTree->setColumnCount( 2 );
					mTree->setHeaderLabels( QStringList() << tr("Filename") << " " );
					mTree->header()->setStretchLastSection( true );
			
					mRoot = new QTreeWidgetItem( QStringList() << TO_QT(name) << QString("(%1 files)").arg(0) );
					mTree->addTopLevelItem( mRoot );
					mRoot->setExpanded( true );
				}
			}
			void Project::updateUi()
			{
				if( mRoot ) mRoot->setText( 1, QString("(%1 files)").arg(mFiles.size()) );

				QTreeWidgetItem * curItem = mTree->currentItem();
				mTools->updateUi( this, curItem );
			}
			
			const ProjectFilePtr& Project::getFileFromWidget( const EditorWidget * item ) const
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
				{
					if( i->second && i->second->getEditor() == item ) return i->second;
				}
				return NULL;
			}
			const ProjectFilePtr& Project::getFileFromWindow( const Window * item ) const
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
				{
					if( i->second && i->second->getWindow() == item ) return i->second;
				}
				return NULL;
			}
			const ProjectFilePtr& Project::getFileFromTree( const QTreeWidgetItem * item ) const
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
				{
					if( i->second && i->second->getItem() == item ) return i->second;
				}
				return NULL;
			}
			bool Project::isFilter( const std::string &name ) const
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
					if( i->first == name ) return false;
				return true;
			}
			bool Project::isFile( const std::string &name ) const
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
					if( i->first == name ) return true;
				return false;
			}
		
			std::string Project::getName() const{ return FROM_QT(mRoot?mRoot->text(0):QString()); }
		
			QTreeWidgetItem * Project::getRootNode(){ return mRoot; }
			const QTreeWidgetItem * Project::getRootNode() const{ return mRoot; }
		
			QMdiArea* Project::getMdiArea(){ return ui->mdiArea; }
			const QMdiArea* Project::getMdiArea() const{ return ui->mdiArea; }
			void Project::removeFile( const ProjectFilePtr &f, bool delete_on_disk_too ){
				if( f ) removeFile( f->getName(), delete_on_disk_too );
			}

			void Project::removeFile( const std::string &name, bool delete_on_disk_too ){
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
					if( i->second && i->second->getName() == name ) 
					{
						if( delete_on_disk_too )
						{
							try{boost::filesystem::remove( getFilePath( name ).str() );}
							catch(...){ Dialog::inform( this, FROM_QT(tr("Invalid path")), FROM_QT(tr("Cannot remove file %1 from disk, already removed").arg(TO_QT(name))) ); }
						}
						delete i->second, i->second=NULL;
						mFiles.erase(i);
						return;
					}
			}
			bool Project::hasFile( const std::string &name ) const
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
					if( i->second && i->second->getName() == name ) 
						return true;
				return false;
			}
			ProjectFilePtr Project::getFile( const std::string &name )
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
					if( i->second && i->second->getName() == name ) 
						return ProjectFilePtr(i->second);
				return ProjectFilePtr();
			}
			ProjectFilePtr Project::openFile( const std::string &name )
			{
				if( mFiles.find( FilePath(name).fileName() ) != mFiles.end() ){
					ProjectFilePtr f(getFile(FilePath(name).fileName()));
					f->save();
					f->getWindow()->close();
					f->getWindow()->show();
					return f;
				}
				FilePath path = getFilePath(name);
				if( !path.exists() )
					throw Lit::ItemMissing( "File '"+name+"'", "Project '"+getName()+"'", EXC_LOC);
				
				std::string cont = FileStream(path).readAll();
				ProjectFile * f(new ProjectFile( FilePath(name).fileName(), this ));
				mFiles[FilePath(name).fileName()] = f;

				ui->mdiArea->activateNextSubWindow();
				f->getWindow()->show();
				f->setText( cont );
				f->save();
				updateUi();
				return ProjectFilePtr(f);
			}
			void Project::on_createShader_triggered( )
			{
				ProgramDef ret(CreateShaderDialog::createShader( this ));
						
				if(ret.hasVertex)
				{
					createFile( ret.vertexShader.name, ret.generateVertex() );
				}
				if(ret.hasPixel)
				{
					createFile( ret.pixelShader.name, ret.generatePixel() );
				}
				if( ret.hasDefinition ) 
				{
					createFile( ret.name, ret.generate() );
				}
			}
			std::vector<ProjectFilePtr> Project::createFile( )
			{
				ProjectItemCreator dlg(this);
				std::vector<ProjectFileDefinition> defs(dlg.exec(this));
				std::vector<ProjectFilePtr> ret;
				for( auto i=defs.begin(); i!=defs.end(); i++ )
				{
					ProjectFileDefinition def(*i);
					if( def.accepted ){
						ret.push_back( createFile( def.name+"."+def.extension, def.content ) );
					}
				}
				return ret;
			}
			ProjectFilePtr Project::createFile( const std::string &name, const std::string &content )
			{
				if( mFiles.find( name ) != mFiles.end() )
					throw Lit::ItemDuplicate( "File '"+FilePath(name).fileName()+"'", " Project '"+getName()+"'", EXC_LOC);
				ProjectFile * f(new ProjectFile( FilePath(name).fileName(), this ));
				ui->mdiArea->activateNextSubWindow();
				f->getWindow()->show();
				mFiles[FilePath(name).fileName()] = f;
				if( content.empty() )
				{
					std::string ext = FilePath(name).extension();
					Ogre::StringUtil::toLowerCase(ext);
					TemplateGenerator gen( name, boost::asio::ip::host_name() );
					if( ext == "material" )
					{
						f->setText( gen.generateMaterial( ) );
					}
					else if( ext == "program" )
					{
						CreateShaderDialog dlg;
						ProgramDef pret(dlg.exec());
						f->setText( pret.generate() );
						if( pret.hasVertex ) createFile( pret.vertexShader.name+"."+pret.vertexShader.language, pret.generateVertex() );
						if( pret.hasPixel ) createFile( pret.pixelShader.name+"."+pret.pixelShader.language, pret.generatePixel() );
					}
					else if( ext == "compositor" )
					{
						f->setText( gen.generateCompositor() );
					}
					else if( ext == "particle" )
					{
						f->setText( gen.generateParticle() );
					}
				}
				else f->setText( content );
				f->save();
				updateUi();
				return ProjectFilePtr(f);
			}
			bool Project::isCreated() const
			{
				return mCreated;
			}
			void Project::newProject( const std::string &name_, bool autosave, bool autocreate )
			{
				std::string name = name_;
				if( name.empty() )
				{
					StringReturn ret;
					while( name.empty() )
					{
						std::string base = "new_project";
						size_t cnt = 0;
						while( (UserPath()+"/"+base).exists() )
							base = String("new_project_")(cnt++).str();
						ret = Dialog::ask( this, FROM_QT(tr("Project Name")), FROM_QT(tr("Name your project:")), base, Dialog::DI_INFORMATION );
						if( ret.rejected() ) break;
						name = ret.value;
					}
				}
				if( name.empty() ) return;
				closeProject( );
				mCreated = true;
				setupUi( name );
				FileSystem::createDirectories( getProjectHome() );
				connect( mTree, SIGNAL(itemClicked( QTreeWidgetItem *, int )), this, SLOT(treeItemClicked( QTreeWidgetItem*, int )) ); 
				if( autocreate ) createFile(name+".material");
				if( autosave ) saveProject( );
				RecentProjectList().add( getProjectHome()+"/"+name+".matproj" );
				updateUi();
			}
			bool Project::isSavePathDefined() const{ return !mSavePath.empty(); }
			const std::string &Project::getSavePath() const{ return mSavePath; }
			void Project::openProject( const std::string &filename )
			{
				std::string path = filename;
				if( path.empty() )
				{
					path = FileDialog::getOpenFileName( NULL, FROM_QT(tr("Open  File")), Material::UserPath(), FROM_QT(tr("Material Project Files"))+" (*.matproj);;" ).str();
				}
				if( path.empty() ) return;
				FileStream strm( path, FileStream::TextRead );
				Ogre::LogManager::getSingleton().logMessage("Open project '"+filename+"'");
				if( !strm.valid() ){
					Ogre::LogManager::getSingleton().logMessage("Cannot open project '"+filename+"', invalid stream !");
					return;
				}
				else
					Ogre::LogManager::getSingleton().logMessage("Valid project !");
					
				mSavePath = path;
			
				StreamPtr strmp( &strm );
				try
				{
					Ogre::LogManager::getSingleton().logMessage("Loading '"+filename+"'");
					mSerialiser->fromStream( strmp, *this );
					updateUi();
					RecentProjectList().add( mSavePath );
				}
				catch( std::exception & e )
				{
					QMessageBox::warning( this, tr("Error"), tr("Cannot load '%1' because: %2").arg(TO_QT(path), TO_QT(e.what())) );
				}
			}
			void Project::saveProject( const std::string &filename )
			{
				std::string path = filename;
				if( path.empty() )
				{
					if( isSavePathDefined() )
						path = getSavePath();
					else path = getFilePath( getName()+".matproj" );
					mSavePath = path;
				}
				if( path.empty() ) return;
				Ogre::LogManager::getSingleton().logMessage("Saving project to '"+filename+"'");
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
				{
					Ogre::LogManager::getSingleton().logMessage("Saving '"+i->second->getName()+"'");
					i->second->save();
				}
				FileStream strm( path, FileStream::TextWrite );
				if( !strm.valid() ) return;
				StreamPtr strmp( &strm );
				mSerialiser->toStream( strmp, *this );
				updateUi();
			}
			void Project::clearFiles()
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
					if( i->second ) delete i->second, i->second=NULL;
				mFiles.clear();
			
				updateUi();
			}
			bool Project::hasModifiedWindow( ) const
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
				{
					if( i->second->isModified() )
						return true;
				}
				return false;
			}
			void Project::saveModifiedWindows( )
			{
				for( auto i=mFiles.begin(); i!=mFiles.end(); i++ )
				{
					if( i->second->isModified() )
						i->second->save();
				}
			}
			bool Project::closeProject()
			{
				if( hasModifiedWindow() )
				{
					int ans = QMessageBox::question( mTree, tr("Save Changes ?"), tr("Some changes have been detected, do you want to save them ?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
					if( ans == QMessageBox::Cancel )
						return false;
					else if( ans == QMessageBox::Yes )
					{
						saveModifiedWindows();
					}
					else
					{
					}
				}
				clearFiles();
				ui->mdiArea->closeAllSubWindows();
				mTree->clear();
				mCreated = false;
				mRoot = NULL;
				mSavePath = "";
				return true;
			}
			QToolBar * Project::getToolBar() { return mTools; }
			const QToolBar * Project::getToolBar() const{ return mTools; }

			QTreeWidget * Project::getTreeWidget() { return mTree; }
			const QTreeWidget * Project::getTreeWidget() const{ return mTree; }
			
			ProjectTools::ProjectTools( Project * proj )
				: QToolBar( proj ), mProject( proj )
			{
				mAddAction = addAction(tr("Add"));
				mAddMenu = new QMenu( proj );
				mAddAction->setMenu(mAddMenu);
				mAddNew = mAddMenu->addAction(QIcon(":/resources/create_file.png"), tr("New Item"));
				mAddExisting = mAddMenu->addAction(QIcon(":/resources/add_file.png"), tr("Existing Item"));
				mAddFilter = mAddMenu->addAction(QIcon(":/resources/folder.png"), tr("Filter"));
				addSeparator();
				mRemoveItem = addAction(QIcon(":/resources/remove_file.png"), tr("Remove Item"));
				addSeparator();
				mItemProperties = addAction(QIcon(":/Icons/settings.png"), tr("Item Properties"));
					
				proj->connect( mAddNew, SIGNAL(triggered(bool)), proj, SLOT(on_actionAddNewItem_triggered()) );
				proj->connect( mAddExisting, SIGNAL(triggered(bool)), proj, SLOT(on_actionAddExistingItem_triggered()) );
				proj->connect( mAddFilter, SIGNAL(triggered(bool)), proj, SLOT(on_actionAddFilter_triggered()) );
				proj->connect( mRemoveItem, SIGNAL(triggered(bool)), proj, SLOT(on_actionRemoveItem_triggered()) );
				proj->connect( mItemProperties, SIGNAL(triggered(bool)), proj, SLOT(on_actionItemProperties_triggered()) );
			}
			ProjectTools::~ProjectTools( )
			{}

			void ProjectTools::updateUi( Project * project, QTreeWidgetItem * item )
			{
				mAddFilter->setEnabled( project->isCreated() );
				mAddExisting->setEnabled( project->isCreated() );
				mAddNew->setEnabled(project->isCreated());
				mRemoveItem->setEnabled( false );
				if( item && project->getRootNode() != item )
				{
					QString name(item->text(0));
					QString desc(tr("Remove %1 '%2'"));

					mRemoveItem->setEnabled( true );
					mRemoveItem->setText(project->isFilter(FROM_QT(name))?desc.arg(tr("Filter"), name):desc.arg(tr("File"), name));
					mRemoveItem->setToolTip(project->isFilter(FROM_QT(name))?desc.arg(tr("Filter"), name):desc.arg(tr("File"), name));
				}
			}
			void ProjectTools::execContextMenu( Project * project, QTreeWidgetItem * item, const QPoint& pos )
			{
				updateUi( project, item );
				QMenu * m = new QMenu( this );
				m->addMenu(mAddMenu);
				m->addSeparator();
				m->addAction(mRemoveItem);
				m->addSeparator();
				m->addAction(mItemProperties);
				m->exec( pos );
			}
		};
	};
};